//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"

#include "CSocket.h"
#define IncrementDllLockCount() do {} while(0)
#define DecrementDllLockCount() do {} while(0)
#include "_CSocket.cpp"
#include <eltypes.h>
#include <stdio.h>
#include "lwip/sockets.h"
#include "arch/cc.h"

#define SOCKET_ERROR            (-1)
//#define LWIP_EFAULT                  -17         /* invalid pointer argument */
//#define LWIP_EINVAL                  -1

extern "C" int g_errno;

char g_localname[25];


UInt32 CSocket::AddRef()
{
    return (UInt32)InterlockedIncrement(&m_cRef);
}

UInt32 CSocket::Release()
{
    UInt32 Refs;
    Refs = (UInt32)InterlockedDecrement(&m_cRef);
    //kprintf("CSocket:Release:%d\n", m_cRef);

    if (Refs == 0) {
        int error = lwip_close(m_socket);
        if (error) {
            assert(0 && "CSocket::Release: soclose error.\n");
        }
        delete this;
    }

    return Refs;
}
PInterface CSocket::Probe(
        /* [in] */ REIID riid)
{
    if (riid == EIID_IInterface) {
        return (IInterface*)this;
    }
    else if (riid == EIID_ISocket) {
        return (ISocket *)this;
    }
    else if(riid == EIID_ISocketEx) {
        return (ISocketEx *)this;
    }
    else if (riid == EIID_CLASS_INFO) {
        return (IInterface *)&ECLSID_CSocket;
    }
    else if (riid == EIID_INTERFACE_INFO) {
        return (IInterface *) &EIID_ISocket;
    }


    return NULL;
}


ECode CSocket::Accept(
        /* [out] */ ISocket ** ppIncomingConnection,
        /* [out] */ SocketAddr* pAddress,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    struct sockaddr sa;
    int so;
    int namelen = sizeof(struct sockaddr);

    if (pAddress == NULL) {
        return E_INVALID_ARGUMENT;
    }
    
    memset(&sa, 0, namelen);

    so = lwip_accept(m_socket, &sa, &namelen);
    if(so < 0) {
		memset(pAddress, 0, sizeof(SocketAddr));
    	*ppIncomingConnection = (ISocket*)NULL;
        *pError = g_errno;
        return S_FALSE;
    }
    memcpy(pAddress, &sa, sizeof(SocketAddr));
    CSocket *pSocket = new CSocket(so);
    if (pSocket == NULL) return E_NOT_ENOUGH_MEMORY;
    pSocket->AddRef();
    *ppIncomingConnection = (ISocket*)pSocket;
    return NOERROR;
}

ECode CSocket::Bind(
        /* [in] */ const SocketAddr & address,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    struct sockaddr sa;
    int error;
    int nameLen = sizeof(struct sockaddr);

    memset(&sa, 0, nameLen);
    memcpy(&sa, &address, sizeof(SocketAddr));
    sa.sa_family = 2;
    sa.sa_len = 0;
    error = lwip_bind(m_socket, &sa, nameLen);

    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::Connect(
        /* [in] */ const SocketAddr & address,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    struct sockaddr sa;
    int error;
    int nameLen = sizeof(struct sockaddr);
    memset(&sa, 0, nameLen);
    memcpy(&sa, &address, sizeof(SocketAddr));
    sa.sa_family = 2;
    sa.sa_len = 0;
    error = lwip_connect(m_socket, &sa, nameLen);
    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::GetPeerName(
        /* [out] */ SocketAddr* pAddress,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    if (pAddress == NULL) {
        return E_INVALID_ARGUMENT;
    }

    struct sockaddr sa;
    int error;
    int nameLen = sizeof(struct sockaddr);

    error = lwip_getpeername(m_socket, &sa, &nameLen);
    
    memcpy(pAddress, &sa, sizeof(SocketAddr));

    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}


ECode CSocket::GetSockName(
        /* [out] */ SocketAddr* pAddress,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    if (pAddress == NULL) {
        return E_INVALID_ARGUMENT;
    }
    struct sockaddr sa;
    int error;
    int nameLen = sizeof(struct sockaddr);

    error = lwip_getsockname(m_socket, &sa, &nameLen);

    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }

    memcpy(pAddress, &sa, sizeof(SocketAddr));
    return NOERROR;
}

ECode CSocket::Listen(
        /* [in] */ Int32 backLog,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int error;
    error = lwip_listen(m_socket, backLog);

    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::Recv(
        /* [in] */ Flags32  flags,
        /* [in] */ Int32  bytesToRecv,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int readlen;
//	char * pBuffer = (char*)ebbBuffer;
    if (pBuffer == NULL) {
        return E_INVALID_ARGUMENT;
    }
    char *pBuf = (char*)malloc(pBuffer->GetCapacity());
    readlen = lwip_recv(m_socket, pBuf, bytesToRecv, flags);
    if (readlen == SOCKET_ERROR) {
//        if(LWIP_EMSGSIZE == g_errno) {
//            ebbBuffer.SetUsed(ebbBuffer.GetCapacity());
//        }
        free(pBuf);
        *pError = g_errno;
        pBuffer->SetUsed(0);
        return S_FALSE;
    }

    pBuffer->Copy((byte*)pBuf, readlen);
    free(pBuf);

    return NOERROR;
}

ECode CSocket::RecvFrom(
        /* [in] */ Flags32  flags,
        /* [in] */ Int32  bytesToRecv,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ SocketAddr* pSourceAddr,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here
    int readlen;
    int addrlen = sizeof(struct sockaddr);
    struct sockaddr from;
    memset(&from, 0, addrlen);

    if (pBuffer == NULL || pSourceAddr == NULL) {
        return E_INVALID_ARGUMENT;
    }
    readlen = lwip_recvfrom(m_socket, (char*)pBuffer->GetPayload(), bytesToRecv, flags
            , &from, &addrlen);
    if (readlen == SOCKET_ERROR) {
//        if(LWIP_EMSGSIZE == g_errno) {
//            ebbFrom.SetUsed(addrlen);
//            ebbBuffer.SetUsed(ebbBuffer.GetCapacity());
            *pError = g_errno;
            return S_FALSE;
//        }
//        else {
//            ebbFrom.SetUsed(0);
//            ebbBuffer.SetUsed(0);
//            *pError = g_errno;
//            return S_FALSE;
//        }
    }
    pBuffer->SetUsed(readlen);
    memcpy(pSourceAddr, &from, sizeof(SocketAddr));

    return NOERROR; 
}

ECode CSocket::Send(
        /* [in] */ Flags32  flags,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32  * pBytesSent,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int sendlen;
    char * buf = (char*)malloc(buffer.GetUsed());
    memcpy(buf, buffer.GetPayload(), buffer.GetUsed());
    sendlen = lwip_send(m_socket, buf, buffer.GetUsed()
            , flags);
    *pBytesSent = sendlen;
    if (sendlen == SOCKET_ERROR) {
        *pError = g_errno;
        free(buf);
        return S_FALSE;
    }

    free(buf);
//	ebbBuffer.SetUsed(sendlen);
	return NOERROR;
}

ECode CSocket::SendTo(
        /* [in] */ Flags32 flags,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */Int32  * pBytesSent,
        /* [in] */ const SocketAddr & targetAddr,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here
    struct sockaddr sto;
    int sendlen;
    //not allow to use user buff directly
    char * buf = (char*)malloc(buffer.GetUsed());
    memcpy(buf, buffer.GetPayload(), buffer.GetUsed());
    
    memset(&sto, 0, sizeof(struct sockaddr));
    memcpy(&sto, &targetAddr, sizeof(SocketAddr));

    sendlen = lwip_sendto(m_socket, buf, buffer.GetUsed(), flags, 
        &sto, sizeof(struct sockaddr));
    if (sendlen == SOCKET_ERROR) {
        *pError = g_errno;
        free(buf);
        return S_FALSE;
    }
    *pBytesSent = sendlen;
    free(buf);
    return NOERROR;
}

ECode CSocket::Shutdown(
        /* [in] */ Int32  target,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int error;

    error = lwip_shutdown(m_socket, target);
    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::EventSelect(
        /* [in] */ IEvent * event,
        /* [in] */ Int32  eventMask)
{
    lwip_eventselect(m_socket, (int)event, eventMask);
    return NOERROR;
}

ECode CSocket::SetSockOpt(
        /* [in] */ Int32  level,
        /* [in] */ Int32  optName,
        /* [in] */ const MemoryBuf & optValue,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int error;
    char * pOptVal = (char*)optValue.GetPayload();
    UInt32 optLen = optValue.GetUsed();

    if (!optValue.GetCapacity() && optLen != 0) {
        error = LWIP_EFAULT;
        goto done;
    }
    if (optLen < 0) {
        error = LWIP_EINVAL;
        goto done;
    }

    //winsock definations are different from usnet
//    if(Level == 6)Level = IPPROTO_TCP;
//    if(Level == IPPROTO_TCP) {
//        if(optName == 1) optName = TCP_NODELAY;
//        else if(optName == 2) optName = TCP_MAXSEG;
//    }
//    if(Level == 0)Level = IPPROTO_IP;
//    if(Level == IPPROTO_IP) {
//        if(optName == 9)optName = IP_MULTICAST_IF;
//        else if(optName == 12)optName = IP_ADD_MEMBERSHIP;
//        else if(optName == 13)optName = IP_DROP_MEMBERSHIP;
//    }
    error = lwip_setsockopt(m_socket, level, optName, pOptVal, optLen);

done:
    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::GetSockOpt(
        /* [in] */ Int32 level,
        /* [in] */ Int32  optName,
        /* [in] */ Int32  optSize,
        /* [out] */ MemoryBuf * pOptValue,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here

    int valsize, error;

    if (pOptValue == NULL) {
        return E_INVALID_ARGUMENT;
    }
    char * pOpt = (char*)pOptValue->GetPayload();

    if (pOptValue->GetCapacity()) {
        valsize = optSize;
        if (valsize < 0) {
            error = LWIP_EINVAL;
            goto done;
        }
    } else {
        valsize = 0;
    }
    //winsock definations are different from usnet
    if(level == 0xffff) level = SOL_SOCKET;
    error = lwip_getsockopt(m_socket, level, optName, pOpt, &valsize);
    pOptValue->SetUsed(valsize);

done:
    if (error) {
        *pError = g_errno;
        return S_FALSE;
    }
    return NOERROR;
}

ECode CSocket::Ioctl(
        /* [in] */ Int32  command,
        /* [in] */ const MemoryBuf & inDataBuf,
        /* [out] */ MemoryBuf * pOutDataBuf,
        /* [out] */ Int32 *pError)
{
    // TODO: Add your code here


#define SIOCGIFADDR 33          /* borrow this macro to get localhost name*/

    if((command&0x000000ff) == SIOCGIFADDR){
        if (pOutDataBuf == NULL) {
            return E_INVALID_ARGUMENT;
        }
        char * netifinfo = (char*)pOutDataBuf->GetPayload();

        if(pOutDataBuf->GetCapacity()<32){
            return E_INVALID_ARGUMENT;
        }
        netifinfo += 16;
        unsigned short* uSin = (unsigned short*)netifinfo;
        *uSin = 0x210;
        netifinfo += 2;
        memset(netifinfo, 0, 14);
        memcpy(netifinfo, &g_localname[0], 14);
        pOutDataBuf->SetUsed(32);
        return NOERROR;
    }

    struct socket *so = (struct socket*)m_socket;
    Byte * pData = (Byte*)malloc(inDataBuf.GetUsed());
    memcpy(pData, inDataBuf.GetPayload(), inDataBuf.GetUsed());
    int ret;

    ret = lwip_ioctl((int)so, command, pData);
    if (ret == -1) {
        *pError = g_errno;
        free(pData);
        return S_FALSE;
    }
    if (pOutDataBuf == NULL) {
        free(pData);
        return E_INVALID_ARGUMENT;
    }
    pOutDataBuf->Copy(pData, inDataBuf.GetUsed());

    free(pData);
    return NOERROR;
}

